/*
 * Socket.cpp
 *
 *  Created on: 06.11.2011
 *      Author: Pavel Sinay
 */

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdio>
#include <fcntl.h>
#include <algorithm>
#include <sys/poll.h>

#include "Socket.h"
#include "../NetException.h"
#include "../constants.h"

namespace net {

Socket::Socket() :
	m_fdsocket(0) {
}

Socket::Socket(int fdsocket) :
	m_fdsocket(fdsocket) {
}

Socket::~Socket() {
	disconnect();
}

bool Socket::isConnected() const{
	return m_fdsocket != 0;
}

void Socket::disconnect() {
	if (m_fdsocket) {
		close(m_fdsocket);
		m_fdsocket = 0;
	}
}

void Socket::waitWriteEnd() {
	timeval timeout;
	timeout.tv_usec = C_TIMEOUT * 1000;
	timeout.tv_sec = 0;

	fd_set writeset;
	FD_ZERO(&writeset);
	FD_SET(m_fdsocket, &writeset);

	if (select(m_fdsocket + 1, NULL, &writeset, NULL, &timeout) <= 0) {
		throwException("Socket::waitWriteEnd(): write timeout");
	} else {
		if (!FD_ISSET(m_fdsocket, &writeset)) {
			throwException("Socket::waitWriteEnd(): write error");
		}
	}
}

void Socket::waitReadReady() {
	timeval timeout;
	timeout.tv_usec = C_TIMEOUT * 1000;
	timeout.tv_sec = 0;

	fd_set readset;
	FD_ZERO(&readset);
	FD_SET(m_fdsocket, &readset);

	if (select(m_fdsocket + 1, &readset, NULL, NULL, &timeout) <= 0) {
		throwException("Socket error: read timeout");
	}
}

int Socket::receiveBlock(char *block, int max_length) {
	waitReadReady();
	int bytes_read = recv(m_fdsocket, block, max_length, 0);

	if (bytes_read < 0) {
		throwException("Socket::receiveBlock(): Reading from socket error");
	}
	return bytes_read;
}

void Socket::ensureWrite() {
	struct pollfd pfd[1];

        pfd[0].fd = m_fdsocket;
        pfd[0].events = POLLOUT | POLLHUP;

        if (poll(pfd, 1, -1) < 0) {
          throwException("Failed to poll.");
        }

        if (pfd[0].revents & POLLHUP) {
          throwException("The peer has gone.");
        }
}


int Socket::sendBlock(const char *block, int length) { 
        ensureWrite();
	int sended = send(m_fdsocket, block, length, 0);
	waitWriteEnd();
	return sended;
}

void Socket::sendMessage(const std::vector<char> & msg) {
	uint32_t size = msg.size();
	sendBlock((char*) &size, sizeof(size));

	unsigned int sended_all = 0;
	int sended = 0;
	do {
		sended = sendBlock(&msg[sended_all], msg.size() - sended_all);
		sended_all += sended;
	} while (sended_all < msg.size());
}

void Socket::receiveMessage(std::vector<char> & msg) {
	msg.clear();
	std::vector<char> block;
	block.resize(C_BLOCK_SIZE);
	size_t read_all = 0;
	int read = 0;
	bool error = false;

	uint32_t size;
	//std::cerr << "Reading size ..." << std::endl;
	read = receiveBlock((char*) &size, sizeof(size));
	//std::cerr << "Size = " << size << std::endl;

	if (read != sizeof(size)) {
		throwException("Failed to read the header");
 	}

	while (read > 0 && read_all < size) {
		//std::cerr << "Reading data ..." << std::endl;
		read = receiveBlock(&block[0], C_BLOCK_SIZE);

		if (read == 0) {
			throwException("Failed to read the block: the peer has gone");
		}

		read_all += read;
		msg.insert(msg.end(), block.begin(), block.begin() + read);

	}
}

/*
bool Socket::isConnected() {
	struct pollfd ufds[1];
	ufds[0].fd = m_fdsocket;
	ufds[0].events = POLLOUT;
	int rv = poll(ufds, 1, 0);
	if (rv == -1 || (ufds[0].revents & POLLERR) || (ufds[0].revents & POLLHUP)) {
		return false;
	} else {
		return true;
	}
}
*/

void Socket::throwException(const std::string &msg) {
	disconnect();
	throw NetException(msg);
}

}

